[Py-OO] Aula 02

Herança

O que você vai aprender nesta aula?

Após o término da aula você terá aprendido:

  • Herança e Herança múltipla
  • Lidando com exceções

Este material usou algumas explicações e exemplos do Capítulo 12 do livro Python Fluente do Luciano Ramalho.

Vamos falar nesta aula sobre herança, que é um dos conceitos mais importantes e utilizados dentro da orientação a objetos. A herança traz várias vantagens como, por exemplo, a reutilização de código que reduz a manutenção de software e permite criar aplicações mais complexas.

"Começamos a insistir na ideia de herança como uma maneira de permitir que novatos desenvolvessem com base em frameworks que poderiam ser projetados somente por experts" (Alan Kay, The Early History of Smalltalk)

Também vamos falar sobre herança múltipla. Muitos programadores chegam ao Python do Java sem ter visto herança múltipla na prática, por esse motivo serão usados exemplos didáticos sobre esse tema usando um projeto Python importante: o framework web Django.

Herança

Na aula passada vimos o seguinte exemplo da classe Cão. Hoje vamos usar herança para especificar raças diferentes de cachorros:


In [1]:
class Cão:
    qtd_patas = 4
    carnívoro = True
    nervoso = False
    
    def __init__(self, nome):
        self.nome = nome
        
    def latir(self, vezes=1):
        """ Latir do cão. Quanto mais nervoso mais late. """
        
        vezes += self.nervoso * vezes
        latido = 'Au! ' * vezes
        print('{}: {}'.format(self.nome, latido))

Vamos brincar um pouco com o cão:


In [2]:
rex = Cão('Rex')
rex.qtd_patas


Out[2]:
4

In [3]:
rex.nome


Out[3]:
'Rex'

In [4]:
rex.latir()


Rex: Au! 

In [5]:
rex.latir(5)


Rex: Au! Au! Au! Au! Au! 

In [6]:
rex.nervoso


Out[6]:
False

In [7]:
rex.nervoso = True
rex.latir()


Rex: Au! Au! 

In [8]:
rex.latir(10)


Rex: Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! 

Agora vamos criar a classe GoldenRetriever como subclasse de cão. Os cachorros da raça golden retriever são conhecidos por serem muito inteligente e amigáveis. Seu nome retriever (pegador) por sua habilidade de pegar os alvos em jogos de caça sem danificá-los. Por esse motivo vamos implementar um método que permita os cães dessa raça pegar e devolver itens.


In [9]:
class GoldenRetriever(Cão):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.itens = []
    
    def pega(self, item):
        """ busca/pega um item quando ordenado """
        self.itens.append(item)
        print('{} pegou {}'.format(self.nome, item))
        
    def devolve(self, item=None):
        """ devolve um item, caso o item não seja especificado retorna o último que pegou """
        if not self.itens:
            print('{} não está segurando item algum!'.format(self.nome))
            return
        
        if not item:
            item = self.itens.pop()
        elif item not in self.itens:
            print('{} não está segurando {}!'.format(self.nome, item))
            return
        else:
            self.itens.remove(item)
        print('{} devolve {}'.format(self.nome, item))
        return item

Linha 1: especificamos que a classe GoldenRetriever herda de Cão.

Linha 2: sobrescrevemos o construtor da superclasse.

Linha 3: repassamos os argumentos para o inicializador de Cão.

Linha 4: criamos um novo atributo itens às instâncias de GoldenRetriever

Por herdar da classe Cão a classe GoldenRetriever recebe todos os métodos e atributos do primeiro:


In [10]:
nana = GoldenRetriever('Nana')
nana.nome


Out[10]:
'Nana'

In [11]:
nana.nervoso


Out[11]:
False

In [12]:
nana.carnívoro


Out[12]:
True

In [13]:
nana.latir()


Nana: Au! 

In [14]:
nana.latir(5)


Nana: Au! Au! Au! Au! Au! 

E temos acesso aos métodos e atributos de GoldenRetriever:


In [15]:
nana.itens


Out[15]:
[]

In [16]:
nana.pega('bola')


Nana pegou bola

In [17]:
nana.itens


Out[17]:
['bola']

In [18]:
nana.devolve()


Nana devolve bola
Out[18]:
'bola'

In [19]:
nana.devolve()


Nana não está segurando item algum!

Python não tem suporte "nativo" a sobrecarga de métodos de mesmo nome. Isso se dá pois a linguagem possui outros métodos de emular essa funcionalidade, como: argumentos padrão e empacotamento de argumentos posicionais e nomeados.

No exemplo anterior, o método GoldenRetriever.devolve() poderia ser escrito com dois metódos: um que não recebe item GoldenRetriever.devolve(self) e outro que recebe um item GoldenRetriever.devolve(self, item). Isso não foi necessário, pois usamos argumento padrão.

Mas, se analisarmos a função devolve com cuidado logo percebemos que ela faz duas coisas diferentes - assim como em uma lista temos os métodos list.pop() e list.remove() e não somente um list.remove() que funciona para todos os casos. Seguindo as melhores práticas de programação seria melhor escrever duas funções diferentes para isso:


In [43]:
class GoldenRetriever(Cão):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.itens = []
    
    def pega(self, item):
        """ busca/pega um item quando ordenado """
        self.itens.append(item)
        print('{} pegou {}'.format(self.nome, item))
        
    def devolve(self, item):
        """ devolve um item, caso o item não seja especificado retorna o último que pegou """
        if not self.itens:
            raise ValueError('{} não está segurando item algum!'.format(self.nome))
        
        if item not in self.itens:
            raise ValueError('{} não está segurando {}!'.format(self.nome, item))
        
        self.itens.remove(item)
        print('{} devolve {}'.format(self.nome, item))
        return item

    def devolve_ultimo(self):
        if not self.itens:
            raise ValueError('{} não está segurando item algum!'.format(self.nome))
        
        return self.itens.pop()

Vamos testar a nova funcionalidade:


In [90]:
toto = GoldenRetriever('Totó')
toto.nome


Out[90]:
'Totó'

In [91]:
toto.itens


Out[91]:
[]

In [103]:
toto.pega('chinelo')


Totó pegou chinelo

In [104]:
toto.pega('bola')


Totó pegou bola

In [105]:
toto.itens


Out[105]:
['chinelo', 'bola']

In [106]:
toto.devolve_ultimo()


Out[106]:
'bola'

In [107]:
toto.devolve('meia')


Totó não está segurando meia!

In [108]:
toto.devolve('chinelo')


Totó devolve chinelo
Out[108]:
'chinelo'

In [109]:
toto.devolve_ultimo()


Totó não está segurando item algum!

Devemos notar que usamos *args e **kwargs na definição da função (linha 2) para repassar a instanciação dos argumentos à classe Cão, dessa maneira se modificarmos esta classe e adicionarmos outros parâmetros, então GoldenRetriever também aceitará esses parâmetros, como podemos ver no seguinte exemplo que redefinimos a classe Cão:


In [110]:
class Cão:
    qtd_patas = 4
    carnívoro = True
    nervoso = False
    
    def __init__(self, nome, data_nascimento=None):
        self.nome = nome
        self.data_nascimento = data_nascimento
        
    def latir(self, vezes=1):
        """ Latir do cão. Quanto mais nervoso mais late. """
        
        vezes += self.nervoso * vezes
        latido = 'Au! ' * vezes
        print('{}: {}'.format(self.nome, latido))

Precisamos recarregar a classe GoldenRetriever para ela herdar da nova superclasse:


In [111]:
class GoldenRetriever(Cão):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.itens = []
    
    def pega(self, item):
        """ busca/pega um item quando ordenado """
        self.itens.append(item)
        print('{} pegou {}'.format(self.nome, item))
        
    def devolve(self, item):
        """ devolve um item, caso o item não seja especificado retorna o último que pegou """
        if not self.itens:
            raise ValueError('{} não está segurando item algum!'.format(self.nome))
        
        if item not in self.itens:
            raise ValueError('{} não está segurando {}!'.format(self.nome, item))
        
        self.itens.remove(item)
        print('{} devolve {}'.format(self.nome, item))
        return item

    def devolve_ultimo(self):
        if not self.itens:
            raise ValueError('{} não está segurando item algum!'.format(self.nome))
        
        return self.itens.pop()

In [112]:
from datetime import date

totó = GoldenRetriever('Totó', date(2016, 4, 4))
totó.data_nascimento


Out[112]:
datetime.date(2016, 4, 4)

In [113]:
print(totó.data_nascimento)


2016-04-04

In [115]:
fido = GoldenRetriever('Fido')
print(fido.data_nascimento)


None

Vamos criar mais uma subclasse de Cão. Dessa vez iremos criar a classe Pinscher, essa raça de cachorro tem a quantidade de raiva inversamente proporcional ao seu tamanho, ou seja, são muito nervosos! E também latem mais cães de outras raças:


In [116]:
class Pinscher(Cão):
    nervoso = True
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    
    def latir(self, vezes=1):
        vezes *= 2
        super().latir(vezes)

In [117]:
mimi = Pinscher('Mimi')
mimi.nervoso


Out[117]:
True

In [118]:
mimi.nome


Out[118]:
'Mimi'

In [119]:
mimi.latir()


Mimi: Au! Au! Au! Au! 

In [120]:
mimi.latir(5)


Mimi: Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! Au! 

Para finalizar vamos criar a classe SãoBernardo que representa cães dessa mesma raça. Esta ficou famosa por diversos filmes de humor que passou dezenas de vezes em certo canal da TV aberta brasileira. Há lendas que dizem que, em países que nevam, o cão São Bernardo leva um pequeno barril de conhaque para resgatar viajantes perdidos na neve:


In [61]:
class SãoBernardo(Cão):
    def __init__(self, *args):
        super().__init__(*args)
        self.doses = 10
        
    def servir(self):
        if self.doses == 0:
            raise ValueError("'Cabou a birita!")
        
        self.doses -= 1
        print('{} serve a birita (restam {} doses)'.format(self.nome, self.doses))

In [62]:
sansao = SãoBernardo('Sansão')
sansao.servir()


Sansão serve a birita (restam 9 doses)

In [63]:
sansao.doses = 1
sansao.servir()


Sansão serve a birita (restam 0 doses)

In [64]:
sansao.servir()


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-64-b7277c8186de> in <module>()
----> 1 sansao.servir()

<ipython-input-61-96128d5ab246> in servir(self)
      6     def servir(self):
      7         if self.doses == 0:
----> 8             raise ValueError("'Cabou a birita!")
      9 
     10         self.doses -= 1

ValueError: 'Cabou a birita!

O Python possui duas funções para verificar instâncias: isinstance(obj, cls) que checa se uma classe é uma instância da classe ou de suas superclasses:


In [65]:
isinstance(sansao, SãoBernardo)


Out[65]:
True

In [72]:
isinstance(sansao, Cão)


Out[72]:
True

In [75]:
isinstance(totó, SãoBernardo)


Out[75]:
False

In [77]:
isinstance(totó, GoldenRetriever)


Out[77]:
True

In [76]:
isinstance(totó, Cão)


Out[76]:
True

Para verificar se o objeto é exatamente o tipo desejado é necessário usar a função embutida type():


In [78]:
type(sansao) is SãoBernardo


Out[78]:
True

In [79]:
type(sansao) is Cão


Out[79]:
False

In [81]:
type(totó) is Cão


Out[81]:
False

Por fim também existe a função issubclass(class, classinfo) que verifica se uma classe é derivada de outra:


In [87]:
issubclass(type(sansao), Cão)


Out[87]:
True

In [88]:
issubclass(bool, int)


Out[88]:
True

Spoiler: o tipo bool é derivado do tipo int. (veremos mais sobre isso na aula sobre python data model)


In [89]:
issubclass(float, int)


Out[89]:
False

Porém o tipo float não.

UML - Unified Modeling Language

O UML é um padrão de modelagem de software para diversos campos do desenvolvimento. O padrão possui diversos diagramas para expressar lógica de negócio e estrutura de programas. Para este curso veremos apenas Diagramas de Classe.

Vamos ver como fica o nosso código de cães em diagramas de classe:

A classe é representada por retângulo e é dividida por três partes:

  1. Nome da classe
  2. Atributos
  3. Métodos

O símbolo antes dos métodos e atributos representa a visibilidade deles. Os símbolos e suas visibilidades são:

+ Público
# Protegido
~ Pacote
- Privado

A seta vazada (existem 3 delas no exemplo) indicam relações de generalização e é usado para indicar se uma classe herda de outra.

Para esta aula iremos usar somente essa pequena parte do padrão de Diagrama de Classes, caso você queira saber mais pode ver este simples tutorial.

Exceptions

Antes de falar sobre herança múltipla, vamos falar primeiro sobre exceções. Exception é uma classe:


In [123]:
ValueError.__name__


Out[123]:
'ValueError'

E herdam de Exception:


In [125]:
issubclass(ValueError, Exception)


Out[125]:
True

Podemos criar nossas próprias exceções herdando de Exception:


In [126]:
class MeuErro(Exception):
    pass

In [128]:
raise MeuErro('deu erro')


---------------------------------------------------------------------------
MeuErro                                   Traceback (most recent call last)
<ipython-input-128-44bbb182b76a> in <module>()
----> 1 raise MeuErro('deu erro')

MeuErro: deu erro

Geralmente não precisamos sobreescreve métodos da superclasse Exception, pois muitas vezes criamos exceções para deixar o programa mais claro. Caso você queira criar um erro com novas funcionalidades consulte a documentação de Exception e a parte tutorial do python que fala sobre exceções.

Lidando com exceções

É possível escrever programas que lidem com exceções:


In [4]:
a, b = 10, 0
a / b


---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-4-b1158bff461f> in <module>()
      1 a, b = 10, 0
----> 2 a / b

ZeroDivisionError: division by zero

In [5]:
try:
    a / b
except ZeroDivisionError:
    print('Olha a divisão por zero aí mano!')


Olha a divisão por zero aí mano!

Também é possível multiplicar múltiplas exceções a serem tratadas:


In [7]:
b = "0"
try:
    a / b
except (ZeroDivisionError, TypeError):
    print('Erro: tem que ver isso daí')


Erro: tem que ver isso daí

Porém lidar com exceções dessa maneira não é recomendado, pois isso pode omitir o nome da exceção e mascarar erros reais! Um jeito melhor de trabalhar com exceções é tratar cada uma de uma maneira:


In [13]:
b = "0"
try:
    a / b
except ZeroDivisionError:
    print('Não é possível dividir por 0')
except TypeError:
    print('Algum tipo está errado')


Algum tipo está errado

In [12]:
b = 0
try:
    a / b
except ZeroDivisionError:
    print('Não é possível dividir por 0')
except TypeError:
    print('Algum tipo está errado')


Não é possível dividir por 0

É possível especificar uma variável para ser atribuída a uma exceção levantada e melhorar o tratamento do erro:


In [27]:
a = {}
try:
    print(a['chave'])
except KeyError as exc:
    print(type(exc))  # imprime o tipo da exceção
    print(exc.args)  # mostra a tupla de argumentos que a exceção recebeu
    print(exc)  # imprime diretamente os argumento


<class 'KeyError'>
('chave',)
'chave'

Para finalizar segue a lista de exceções padrão. A explicação de cada exceção está na documentação:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

Herança múltipla

O Python oferece suporte a herança múltipla. A forma de usar herança múltipla é parecida com a herança simples, porém a subclasse herda de mais de uma classe, por exemplo:

class Subclasse(Base1, Base2, Base3):
    ...
    ...

Algumas linguagens famosas não implementam herança múltipla como Java, C# e Ruby (elas possuem outras técnicas para prover essa funcionalidade), pois quando usada de forma incorreta pode resultar em sistemas ambíguos e difíceis de entender.

Um problema muito comum de herança múltipla é o Problema do losango, em que aparece ambiguidade quando uma classe A implementa um método que é sobrescrito nas subclasses B e C e quando D, que não sobrescreveu esse método, invoca esse método qual deve ser chamado: o de C ou de D?

Vamos fazer um exemplo que implementa o problema do losango e mostaremos como o Python o resolve:


In [3]:
class A:
    def ping(self):
        print('ping', self)

class B(A):
    def pong(self):
        print('pong', self)

class C(A):
    def pong(self):
        print('PONG', self)

class D(B, C):
    def ping(self):
        super().ping()
        print('post-ping:', self)
        
    def pingpong(self):
        self.ping()
        super().ping()
        self.pong()
        C.pong(self)

In [5]:
d = D()
d.pong()


pong <__main__.D object at 0x7f6bfcafb438>

Como vemos, o método chamado pela instância da classe D foi B.pong(), p|odemos chamar explicitamente C.pong() passando a instância como argumento explícito:


In [6]:
C.pong(d)


PONG <__main__.D object at 0x7f6bfcafb438>

Essa maneira de chamar métodos só é utilizando para resolver ambiguidade causada pela herança múltipla. O jeito recomendado de delegar chamadas de métodos a superclasses é usar a função embutida super(), pois é mais seguro e resistente a mudanças futuras, principalmente quando chamamos métodos em um framework ou em qualquer hierarquia de classes sobre a qual você não tenha controle.

Vamos ver como a função D.ping() se comporta:


In [34]:
d.ping()


ping <__main__.D object at 0x7fcddcd2bd68>
post-ping: <__main__.D object at 0x7fcddcd2bd68>

d.ping() chamou a função A.ping() (que imprimiu ping) e imprimiu post-ping

Agora vamos analisar as chamadas do método pingpong:


In [35]:
d.pingpong()


ping <__main__.D object at 0x7fcddcd2bd68>
post-ping: <__main__.D object at 0x7fcddcd2bd68>
ping <__main__.D object at 0x7fcddcd2bd68>
pong <__main__.D object at 0x7fcddcd2bd68>
PONG <__main__.D object at 0x7fcddcd2bd68>

Primeiro foi chamado D.ping() que chama A.ping() e imprime post-ping

Depois foi chamado super().ping() que chama somente A.ping()

Foi chamado super().pong() que chamou B.pong()

E, por fim, C.pong(self) foi chamado diretamente, passando a instância de D.

Agora que entendemos como esse exemplo do problema do diagrama é rodado, podemos falar sobre como o Python resolve essa ambiguidade: percorrendo o grafo de herança de uma forma específica: o MRO - Method Resolution Order ou Ordem de Resolução dos Métodos.

O Python procura o método na própria classe, depois em cada superclasse (sem repetição, caso exista haja sobreposição na hierarquia) da esquerda para a direita, depois procura nas superclasses da superclasses da esquerda para direita até chegar a object (herdado por todas as classes).

Todos os objetos das classes possuem um atributo chamado __mro__ que apresenta uma tupla de referências às superclasses na ordem MRO, da classe atual até a classe object. Vamos ver qual é o MRO da classe D:


In [18]:
D.__mro__


Out[18]:
(__main__.D, __main__.B, __main__.C, __main__.A, object)

Aqui comprovamos o que foi dito anteriormente: primeiro um atributo é buscado na seguinte ordem: D, B, C, A e object.

Vamos inspecionar o atributo __mro__ de outras classes:


In [36]:
bool.__mro__


Out[36]:
(bool, int, object)

In [40]:
from numbers import Integral
Integral.__mro__


Out[40]:
(numbers.Integral,
 numbers.Rational,
 numbers.Real,
 numbers.Complex,
 numbers.Number,
 object)

In [41]:
from io import StringIO
StringIO.__mro__


Out[41]:
(_io.StringIO, _io._TextIOBase, _io._IOBase, object)

Lidando com herança múltipla

Como dito anteriormente a herança múltipla pode tornar o código de um software muito complicado e frágil. A seguir consta algumas dicas para deixar grafos de classes mais simples:

  • Faça a distinção entre herança de interface (para criar um subtipo, implicando em uma relação "é um") e herança de implementação (para evitar duplicação de código por meio de reutilização)

  • Deixe as interfaces explícitas com ABCs

  • Use mixins e explicite as mixins pelo nome

  • Uma ABC pode ser um mixin, mas não o contrário

  • Não herde de mais de uma classe concreta

  • Ofereça classes agregadas aos usuários

  • "Prefira composição de objetos à herança de classe" (GoF, Padrões de Projeto)

Mixins

"Mixin é uma classe que provê funcionalidade para ser herdada, mas não instanciada sozinha. [...] pode ser usada para melhorar funcionalidades e comportamentos de classes" (Greenfield e Greenfield, Two Scoops of Django 1.8)

Regra de uso de mixins:

  1. A classe base deve estar sempre a direita
  2. Os mixins devem estar a esquerda da classe base
  3. As classes e mixins devem herdar de object (isso já é feito por padrão no Python 3)

Herança múltipla na prática: Django

O django é um framework web Python muito popular. Ele facilita a criação de views oferecendo views genéricas baseadas em classe - as Class Based Views (CBV - que implementam tarefas comuns na criação de sistema webs como listar objetos, exibir páginas estáticas e criar views de redirecionamento.

As Class Based Views foram criadas usando herança múltipla e seguem as dicas citadas acima. Abaixo segue o Diagrama de Classes para podermos entender como a herança múltipla pode ser usada de maneira positiva na prática:

TemplateView e RedirectView

A TemplateView exibe o conteúdo de uma página estática. Enquanto que o RedirectView simplesmente redireciona uma página para outra.

Vamos ver como o python resolve a herança de TemplateView verificando seu atributo __mro__:


In [4]:
from django.views.generic import TemplateView

TemplateView.__mro__


Out[4]:
(django.views.generic.base.TemplateView,
 django.views.generic.base.TemplateResponseMixin,
 django.views.generic.base.ContextMixin,
 django.views.generic.base.View,
 object)

Agora verificaremos o RedirectView.__mro__:


In [5]:
from django.views.generic import RedirectView

RedirectView.__mro__


Out[5]:
(django.views.generic.base.RedirectView,
 django.views.generic.base.View,
 object)
DetailView

A DetailView cria uma página para exibição da informação de um único modelo. Por exemplo podemos utilizá-la em um e-commerce para facilitar a criação de uma página que exibe as informações de apenas um produto.


In [6]:
from django.views.generic import DetailView

DetailView.__mro__


Out[6]:
(django.views.generic.detail.DetailView,
 django.views.generic.detail.SingleObjectTemplateResponseMixin,
 django.views.generic.base.TemplateResponseMixin,
 django.views.generic.detail.BaseDetailView,
 django.views.generic.detail.SingleObjectMixin,
 django.views.generic.base.ContextMixin,
 django.views.generic.base.View,
 object)
ListView

A ListView - favorita dos usuários de CBV - facilita a criação de views que permitem a listagem de modelos. Como por exemplo a listagem de todos os produtos do site (ou de apenas os de algum tipo, categoria etc.).


In [7]:
from django.views.generic import ListView

ListView.__mro__


Out[7]:
(django.views.generic.list.ListView,
 django.views.generic.list.MultipleObjectTemplateResponseMixin,
 django.views.generic.base.TemplateResponseMixin,
 django.views.generic.list.BaseListView,
 django.views.generic.list.MultipleObjectMixin,
 django.views.generic.base.ContextMixin,
 django.views.generic.base.View,
 object)
DeleteView

Por fim temos a DeleteView que permite a remoção de um modelo.


In [8]:
from django.views.generic import DeleteView

DeleteView.__mro__


Out[8]:
(django.views.generic.edit.DeleteView,
 django.views.generic.detail.SingleObjectTemplateResponseMixin,
 django.views.generic.base.TemplateResponseMixin,
 django.views.generic.edit.BaseDeleteView,
 django.views.generic.edit.DeletionMixin,
 django.views.generic.detail.BaseDetailView,
 django.views.generic.detail.SingleObjectMixin,
 django.views.generic.base.ContextMixin,
 django.views.generic.base.View,
 object)

Fim da aula 02